{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Decorators Application (Memoization)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's go back to our Fibonacci example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def fib(n):\n",
    "    print ('Calculating fib({0})'.format(n))\n",
    "    return 1 if n < 3 else fib(n-1) + fib(n-2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we run this, we see that it is quite inefficient, as the same Fibonacci numbers get calculated multiple times:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating fib(6)\n",
      "Calculating fib(5)\n",
      "Calculating fib(4)\n",
      "Calculating fib(3)\n",
      "Calculating fib(2)\n",
      "Calculating fib(1)\n",
      "Calculating fib(2)\n",
      "Calculating fib(3)\n",
      "Calculating fib(2)\n",
      "Calculating fib(1)\n",
      "Calculating fib(4)\n",
      "Calculating fib(3)\n",
      "Calculating fib(2)\n",
      "Calculating fib(1)\n",
      "Calculating fib(2)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "8"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fib(6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It would be better if we could somehow \"store\" these results, so if we have calculated `fib(4)` and `fib(3)` before, we could simply recall the these values when calculating `fib(5) = fib(4) + fib(3)` instead of recalculating them.\n",
    "\n",
    "This concept of improving the efficiency of our code by caching pre-calculated values so they do not need to be re-calcualted every time, is called \"memoization\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can approach this using a simple class and a dictionary that stores any Fibonacci number that's already been calculated:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class Fib:\n",
    "    def __init__(self):\n",
    "        self.cache = {1: 1, 2: 1}\n",
    "    \n",
    "    def fib(self, n):\n",
    "        if n not in self.cache:\n",
    "            print('Calculating fib({0})'.format(n))\n",
    "            self.cache[n] = self.fib(n-1) + self.fib(n-2)\n",
    "        return self.cache[n]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "f = Fib()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f.fib(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating fib(6)\n",
      "Calculating fib(5)\n",
      "Calculating fib(4)\n",
      "Calculating fib(3)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "8"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f.fib(6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating fib(7)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "13"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f.fib(7)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's see how we could rewrite this using a closure:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def fib():\n",
    "    cache = {1: 1, 2: 2}\n",
    "    \n",
    "    def calc_fib(n):\n",
    "        if n not in cache:\n",
    "            print('Calculating fib({0})'.format(n))\n",
    "            cache[n] = calc_fib(n-1) + calc_fib(n-2)\n",
    "        return cache[n]\n",
    "    \n",
    "    return calc_fib"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "f = fib()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating fib(10)\n",
      "Calculating fib(9)\n",
      "Calculating fib(8)\n",
      "Calculating fib(7)\n",
      "Calculating fib(6)\n",
      "Calculating fib(5)\n",
      "Calculating fib(4)\n",
      "Calculating fib(3)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "89"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's see how we would implement this using a decorator:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from functools import wraps\n",
    "\n",
    "def memoize_fib(fn):\n",
    "    cache = dict()\n",
    "    \n",
    "    @wraps(fn)\n",
    "    def inner(n):\n",
    "        if n not in cache:\n",
    "            cache[n] = fn(n)\n",
    "        return cache[n]\n",
    "    \n",
    "    return inner"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "@memoize_fib\n",
    "def fib(n):\n",
    "    print ('Calculating fib({0})'.format(n))\n",
    "    return 1 if n < 3 else fib(n-1) + fib(n-2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating fib(3)\n",
      "Calculating fib(2)\n",
      "Calculating fib(1)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fib(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating fib(10)\n",
      "Calculating fib(9)\n",
      "Calculating fib(8)\n",
      "Calculating fib(7)\n",
      "Calculating fib(6)\n",
      "Calculating fib(5)\n",
      "Calculating fib(4)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "55"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fib(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "8"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fib(6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, we are hitting the cache when the values are available.\n",
    "\n",
    "Now, we made our memoization decorator \"hardcoded\" to single argument functions - we could make it more generic.\n",
    "\n",
    "For example, to handle an arbitrary number of positional arguments and keyword-only arguments we could do the following:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def memoize(fn):\n",
    "    cache = dict()\n",
    "    \n",
    "    @wraps(fn)\n",
    "    def inner(*args):\n",
    "        if args not in cache:\n",
    "            cache[args] = fn(*args)\n",
    "        return cache[args]\n",
    "    \n",
    "    return inner"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "@memoize\n",
    "def fib(n):\n",
    "    print ('Calculating fib({0})'.format(n))\n",
    "    return 1 if n < 3 else fib(n-1) + fib(n-2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating fib(6)\n",
      "Calculating fib(5)\n",
      "Calculating fib(4)\n",
      "Calculating fib(3)\n",
      "Calculating fib(2)\n",
      "Calculating fib(1)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "8"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fib(6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating fib(7)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "13"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fib(7)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, with this rather generic memoization decorator we can memoize other functions too:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def fact(n):\n",
    "    print('Calculating {0}!'.format(n))\n",
    "    return 1 if n < 2 else n * fact(n-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating 5!\n",
      "Calculating 4!\n",
      "Calculating 3!\n",
      "Calculating 2!\n",
      "Calculating 1!\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "120"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fact(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating 5!\n",
      "Calculating 4!\n",
      "Calculating 3!\n",
      "Calculating 2!\n",
      "Calculating 1!\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "120"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fact(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And memoizing it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "@memoize\n",
    "def fact(n):\n",
    "    print('Calculating {0}!'.format(n))\n",
    "    return 1 if n < 2 else n * fact(n-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating 6!\n",
      "Calculating 5!\n",
      "Calculating 4!\n",
      "Calculating 3!\n",
      "Calculating 2!\n",
      "Calculating 1!\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "720"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fact(6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "720"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fact(6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our simple memoizer has a drawback however:\n",
    "* the cache size is unbounded - probably not a good thing! In general we want to limit the cache to a certain number of entries, balancing computational efficiency vs memory utilization.\n",
    "* we are not handling **kwargs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Memoization is such a common thing to do that Python actually has a memoization decorator built for us!\n",
    "\n",
    "It's in the, you guessed it, **functools** module, and is called **lru_cache** and is going to be quite a bit more efficient compared to the rudimentary memoization example we did above.\n",
    "\n",
    "[LRU Cache = Least Recently Used caching: since the cache is not unlimited, at some point cached entries need to be discarded, and the least recently used entries are discarded first]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from functools import lru_cache"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "@lru_cache()\n",
    "def fact(n):\n",
    "    print(\"Calculating fact({0})\".format(n))\n",
    "    return 1 if n < 2 else n * fact(n-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating fact(5)\n",
      "Calculating fact(4)\n",
      "Calculating fact(3)\n",
      "Calculating fact(2)\n",
      "Calculating fact(1)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "120"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fact(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "24"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fact(4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, `fact(4)` was returned via a cached entry!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Same thing with our Fibonacci function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "@lru_cache()\n",
    "def fib(n):\n",
    "    print(\"Calculating fib({0})\".format(n))\n",
    "    return 1 if n < 3 else fib(n-1) + fib(n-2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating fib(6)\n",
      "Calculating fib(5)\n",
      "Calculating fib(4)\n",
      "Calculating fib(3)\n",
      "Calculating fib(2)\n",
      "Calculating fib(1)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "8"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fib(6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "5"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fib(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Recall from a few videos back that we timed the calculation for Fibonacci numbers. Calculating fib(35) took several seconds - every time..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from time import perf_counter"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def fib_no_memo(n):\n",
    "    return 1 if n < 3 else fib_no_memo(n-1) + fib_no_memo(n-2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "result=9227465, elapsed: 2.939012289158911s\n"
     ]
    }
   ],
   "source": [
    "start = perf_counter()\n",
    "result = fib_no_memo(35)\n",
    "print(\"result={0}, elapsed: {1}s\".format(result, perf_counter() - start))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "@lru_cache()\n",
    "def fib_memo(n):\n",
    "    return 1 if n < 3 else fib_memo(n-1) + fib_memo(n-2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "result=9227465, elapsed: 9.83349429017899e-05s\n"
     ]
    }
   ],
   "source": [
    "start = perf_counter()\n",
    "result = fib_memo(35)\n",
    "print(\"result={0}, elapsed: {1}s\".format(result, perf_counter() - start))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And if we make the calls again:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "result=9227465, elapsed: 2.782454120518548s\n"
     ]
    }
   ],
   "source": [
    "start = perf_counter()\n",
    "result = fib_no_memo(35)\n",
    "print(\"result={0}, elapsed: {1}s\".format(result, perf_counter() - start))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "result=9227465, elapsed: 5.6617088337596044e-05s\n"
     ]
    }
   ],
   "source": [
    "start = perf_counter()\n",
    "result = fib_memo(35)\n",
    "print(\"result={0}, elapsed: {1}s\".format(result, perf_counter() - start))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You may have noticed that the `lru_cache` decorator was implemented using `()` - we'll see more on this later, but that's because decorators can themselves have parameters (beyond the function being decorated)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One of the arguments to the `lru_cache` decorator is the size of the cache - it defaults to 128 items, but we can easily change this - for performance reasons use powers of 2 for the cache size (or None for unbounded cache):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "@lru_cache(maxsize=8)\n",
    "def fib(n):\n",
    "    print(\"Calculating fib({0})\".format(n))\n",
    "    return 1 if n < 3 else fib(n-1) + fib(n-2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating fib(10)\n",
      "Calculating fib(9)\n",
      "Calculating fib(8)\n",
      "Calculating fib(7)\n",
      "Calculating fib(6)\n",
      "Calculating fib(5)\n",
      "Calculating fib(4)\n",
      "Calculating fib(3)\n",
      "Calculating fib(2)\n",
      "Calculating fib(1)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "55"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fib(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating fib(20)\n",
      "Calculating fib(19)\n",
      "Calculating fib(18)\n",
      "Calculating fib(17)\n",
      "Calculating fib(16)\n",
      "Calculating fib(15)\n",
      "Calculating fib(14)\n",
      "Calculating fib(13)\n",
      "Calculating fib(12)\n",
      "Calculating fib(11)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "6765"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fib(20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Calculating fib(10)\n",
      "Calculating fib(9)\n",
      "Calculating fib(8)\n",
      "Calculating fib(7)\n",
      "Calculating fib(6)\n",
      "Calculating fib(5)\n",
      "Calculating fib(4)\n",
      "Calculating fib(3)\n",
      "Calculating fib(2)\n",
      "Calculating fib(1)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "55"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fib(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note how Python had to recalculate `fib` for `10, 9,` etc. This is because the cache can only contain 10 items, so when we calculated `fib(20)`, it stored fib for `20, 19, ..., 11` (10 items) and therefore the oldest items fib `10, 9, ..., 1` were removed from the cache to make space."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
